package com.czgj.JGB28181.web.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.czgj.JGB28181.bean.Device;
import com.czgj.JGB28181.bean.DeviceChannel;
import com.czgj.JGB28181.bean.PushStreamDevice;
import com.czgj.JGB28181.common.constants.ResultConstants;
import com.czgj.JGB28181.common.result.GBResult;
import com.czgj.JGB28181.common.result.MediaData;
import com.czgj.JGB28181.common.utils.IDUtils;
import com.czgj.JGB28181.common.utils.RedisUtil;
import com.czgj.JGB28181.common.utils.StreamNameUtils;
import com.czgj.JGB28181.media.callback.OnProcessListener;
import com.czgj.JGB28181.media.server.Server;
import com.czgj.JGB28181.media.server.TCPServer;
import com.czgj.JGB28181.media.server.UDPServer;
import com.czgj.JGB28181.media.server.remux.Observer;
import com.czgj.JGB28181.media.server.remux.RtmpPusher;
import com.czgj.JGB28181.media.session.PushStreamDeviceManager;
import com.czgj.JGB28181.message.config.ConfigProperties;
import com.czgj.JGB28181.message.session.MessageManager;
import com.czgj.JGB28181.message.session.SyncFuture;
import com.czgj.JGB28181.request.SipLayer;
import com.czgj.JGB28181.web.service.ActionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.sip.Dialog;
import javax.sip.SipException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 行动操作的实现类
 */
@Service
@EnableConfigurationProperties(ConfigProperties.class)
public class ActionServiceImpl implements ActionService, OnProcessListener {

    /**
     * 日志
     */
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private SipLayer mSipLayer;

    /**
     * 消息
     */
    private MessageManager mMessageManager = MessageManager.getInstance();

    /**
     * 会话管理的Map (单例)
     */
    private PushStreamDeviceManager mPushStreamDeviceManager = PushStreamDeviceManager.getInstance();

    @Value("${config.pullRtmpAddress}")
    private String pullRtmpAddress;


    @Value("${config.pushRtmpAddress}")
    private String pushRtmpAddress;

    @Value("${config.checkSsrc}")
    private boolean checkSsrc;


    /**
     * 获取到流信息
     *
     * @param deviceId      设备的用户ID信息
     * @param channelId     通道的ID
     * @param mediaProtocol 类型
     * @return 流的信息
     */
    @Override
    public GBResult play(String deviceId, String channelId, String mediaProtocol) {
//      1 : 返回对象
        GBResult result = null;
        try {
//      2.从redis查找设备，如果没有注册，则返回离线
            String deviceStr = RedisUtil.get(deviceId);
            if (StringUtils.isEmpty(deviceStr)) {
                return GBResult.build(ResultConstants.DEVICE_OFF_LINE_CODE, ResultConstants.DEVICE_OFF_LINE);
            }
//      3.设备在线，先检查是否正在推流
//          3.1 : 如果正在推流，直接返回rtmp地址
//          3.2 : 生成流的名字
            String streamName = StreamNameUtils.play(deviceId, channelId);
//          3.3 : 获取到当前设备的推流信息
            PushStreamDevice pushStreamDevice = mPushStreamDeviceManager.get(streamName);
            if (pushStreamDevice != null) {
//              3.3.1 : 已经推流,则把流信息 ,返回
                return GBResult.ok(new MediaData(pushStreamDevice.getPullRtmpAddress(), pushStreamDevice.getCallId()));
            }
//      4 : 检查通道是否存在 (把注册信息转换成对象)
            Device device = JSONObject.parseObject(deviceStr, Device.class);
//          4.1 : 获取通道/判断通道高
            Map<String, DeviceChannel> channelMap = device.getChannelMap();
            if (channelMap == null || !channelMap.containsKey(channelId)) {
//          4.2 : 返回错误响应 ,通道不在线
                return GBResult.build(ResultConstants.CHANNEL_NO_EXIST_CODE, ResultConstants.CHANNEL_NO_EXIST);
            }
//      5 : 当前请求 , 是否是TCP
            boolean isTcp = mediaProtocol.toUpperCase().equals(SipLayer.TCP);
//      6 : 下发指令 (获取到指令的ID)
            String callId = IDUtils.id();
//          6.1 : getPort可能耗时，在外面调用。
            int port = mSipLayer.getPort(isTcp);
//          6.2 : 获取到ssrc (每一个流需要生成一个 (唯一))
            String ssrc = mSipLayer.getSsrc(true);
//          6.3 : 推流
            mSipLayer.sendInvite(device, SipLayer.SESSION_NAME_PLAY, callId, channelId, port, ssrc, isTcp);
//          6.4 : 等待指令响应
            SyncFuture<?> receive = mMessageManager.receive(callId);
            Dialog response = (Dialog) receive.get(3, TimeUnit.SECONDS);
//      7 : 响应成功，创建推流session
            if (response != null) {
//          7.1 : 创建拉流的URL rtmp://127.0.0.1:1935/hls/#{streamName}
                String address = pushRtmpAddress.concat(streamName);
//          7.2 : 创建请求对象 (基于TCP/UDP判断)
                Server server = isTcp ? new TCPServer() : new UDPServer();
//          7.3 : 创建RTMP推流
                Observer observer = new RtmpPusher(address, callId);
                server.subscribe(observer);
//          7.4 : 推流信息
                pushStreamDevice = new PushStreamDevice(deviceId, Integer.valueOf(ssrc), callId, streamName, port, isTcp, server,
                        observer, address);
//          7.5 : 设置通道连接
                pushStreamDevice.setDialog(response);
//          7.6 : 启动服务
                server.startServer(pushStreamDevice.getFrameDeque(), Integer.valueOf(ssrc), port, false);
                observer.startRemux();

                observer.setOnProcessListener(this);
//          7.7 : 添加会话管理
                mPushStreamDeviceManager.put(streamName, callId, Integer.valueOf(ssrc), pushStreamDevice);
//          7.8 : 返回流信息
                result = GBResult.ok(new MediaData(pushStreamDevice.getPullRtmpAddress(), pushStreamDevice.getCallId()));
            } else {
//      8 : 响应失败，删除推流session
                mMessageManager.remove(callId);
//          8.1 : 返回失败的响应
                result = GBResult.build(ResultConstants.COMMAND_NO_RESPONSE_CODE, ResultConstants.COMMAND_NO_RESPONSE);
            }
        } catch (Exception e) {
            logger.error("推流失败 ; 系统出现异常 : {} : {} : {}", deviceId, channelId, mediaProtocol, e);
            result = GBResult.build(ResultConstants.SYSTEM_ERROR_CODE, ResultConstants.SYSTEM_ERROR);
        }
        return result;
    }

    @Override
    public GBResult bye(String callId) {
        try {
            mSipLayer.sendBye(callId);
        } catch (SipException e) {
            logger.error("ByeBye出现异常 ; 系统出现异常 : {} :", callId, e);
        }
        return GBResult.ok();
    }

    /**
     * 出现异常
     */
    @Override
    public void onError(String callId) {
        try {
            mSipLayer.sendBye(callId);
        } catch (SipException e) {
            logger.error("连接出现异常 : 强制进行ByeBye : {} :", callId, e);
        }
    }
}
